package com.easylinkin.linkappapi.ruleengine.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.easylinkin.linkappapi.alarm.entity.Alarm;
import com.easylinkin.linkappapi.alarm.service.AlarmService;
import com.easylinkin.linkappapi.common.service.CommonService;
import com.easylinkin.linkappapi.common.utils.DateUtil;
import com.easylinkin.linkappapi.device.entity.Device;
import com.easylinkin.linkappapi.device.service.SprayRecordsService;
import com.easylinkin.linkappapi.deviceattribute.constant.PhysicalModelTypeConstant;
import com.easylinkin.linkappapi.deviceparm.entity.DeviceParm;
import com.easylinkin.linkappapi.deviceservice.entity.DeviceServices;
import com.easylinkin.linkappapi.deviceservice.service.DeviceServiceService;
import com.easylinkin.linkappapi.linkage.constant.LinkageLogConstant;
import com.easylinkin.linkappapi.linkage.entity.LinkageConfigRefDownDevice;
import com.easylinkin.linkappapi.linkage.entity.LinkageConfigRelateServiceParm;
import com.easylinkin.linkappapi.linkage.entity.LinkageLog;
import com.easylinkin.linkappapi.linkage.mapper.LinkageConfigRelateServiceParmMapper;
import com.easylinkin.linkappapi.linkage.service.LinkageConfigRefDownDeviceService;
import com.easylinkin.linkappapi.linkage.service.LinkageConfigRelateServiceParmService;
import com.easylinkin.linkappapi.linkage.service.LinkageLogService;
import com.easylinkin.linkappapi.openapi.dto.ApiDownResultDTO;
import com.easylinkin.linkappapi.ruleengine.constant.RuleExecutionConstant;
import com.easylinkin.linkappapi.ruleengine.entity.RuleExecution;
import com.easylinkin.linkappapi.ruleengine.entity.RuleExecutionRefAlarmPersonContact;
import com.easylinkin.linkappapi.ruleengine.mapper.RuleExecutionMapper;
import com.easylinkin.linkappapi.ruleengine.service.RuleExecutionRefAlarmPersonContactService;
import com.easylinkin.linkappapi.ruleengine.service.RuleExecutionService;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import javax.annotation.Resource;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;


/**
 * <p>
 * 执行器 服务实现类
 * </p>
 *
 * @author TongJie
 * @since 2020-08-24
 */
@Service
public class RuleExecutionServiceImpl extends ServiceImpl<RuleExecutionMapper, RuleExecution> implements RuleExecutionService {


    @Resource
    private CommonService commonService;
    @Resource
    private LinkageConfigRelateServiceParmService linkageConfigRelateServiceParmService;
    @Resource
    private LinkageConfigRefDownDeviceService linkageConfigRefDownDeviceService;
    @Resource
    private DeviceServiceService deviceServiceService;
    @Resource
    private LinkageConfigRelateServiceParmMapper linkageConfigRelateServiceParmMapper;
    @Resource
    private LinkageLogService linkageLogService;
    @Resource
    private AlarmService alarmService;
    @Resource
    private RuleExecutionRefAlarmPersonContactService ruleExecutionRefAlarmPersonContactService;
    @Resource
    private SprayRecordsService sprayRecordsService;


    ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("rebuildVideoDownParamAndUpdateAlarm-thread-%d").build();

    ExecutorService pool = new ThreadPoolExecutor(2, 4, 100L,
        TimeUnit.MILLISECONDS,
        new LinkedBlockingDeque<>(256),
        namedThreadFactory,
        new ThreadPoolExecutor.AbortPolicy());


    /**
     * 日志记录对象
     */
    private static final Logger LOGGER = LoggerFactory.getLogger(RuleExecutionServiceImpl.class);
    /**
     * 回放标志
     */
    private final String PLAY_BACK = "playback";

    /**
     * 通断控制
     */
    private final String SWITCH_CONTROL = "switch_control";

    @Value("${nb.nbVideoUrl}")
    private String nbVideoUrl;

    /**
     * 校验重复
     */
    private void validRepeat(RuleExecution ruleExecution) {
    }


    /**
     * 校验参数必填
     */
    private void validParamRequired(RuleExecution ruleExecution) {
        Assert.notNull(ruleExecution, "参数为空");
        Assert.notNull(ruleExecution.getType(), "getType 为空");
        Assert.isTrue(StringUtils.isNotBlank(ruleExecution.getRuleEngineId()), "getRuleEngineId 为空");
    }

    /**
     * 校验参数格式
     */
    private void validParamFormat(RuleExecution ruleExecution) {
        Assert.isTrue(ruleExecution.getRuleEngineId() == null || ruleExecution.getRuleEngineId().length() <= 32, "getRuleEngineId 超长");
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void add(RuleExecution ruleExecution) {
        validParamRequired(ruleExecution);
        validParamFormat(ruleExecution);
        validRepeat(ruleExecution);
        if (RuleExecutionConstant.EXECUTION_TYPE_ALARM.equals(ruleExecution.getType())) {
            Assert.hasLength(ruleExecution.getAlarmTemplateId(), "type为2，alarmTemplateId 不能为空");
        } else {
            ruleExecution.setAlarmTemplate(null);
        }
        Date now = new Date();
        ruleExecution.setCreateTime(now);
        ruleExecution.setModifyTime(now);
        ruleExecution.setSortFlag(DateUtil.getyyyyMMddHHmmssSSSNumberString(now));

        save(ruleExecution);

        if (RuleExecutionConstant.EXECUTION_TYPE_ALARM.equals(ruleExecution.getType())) {
            List<RuleExecutionRefAlarmPersonContact> ruleExecutionRefAlarmPersonContactList = ruleExecution.getRuleExecutionRefAlarmPersonContactList();
            for (RuleExecutionRefAlarmPersonContact ruleExecutionRefAlarmPersonContact : ruleExecutionRefAlarmPersonContactList) {
                ruleExecutionRefAlarmPersonContact.setRuleExecutionId(ruleExecution.getId());
                ruleExecutionRefAlarmPersonContactService.save(ruleExecutionRefAlarmPersonContact);
            }
        }
        if (RuleExecutionConstant.EXECUTION_TYPE_DOWN_SERVICE.equals(ruleExecution.getType())) {
//            then 保存关联 服务参数
            List<LinkageConfigRelateServiceParm> linkageConfigRelateServiceParms = ruleExecution.getLinkageConfigRelateServiceParms();
            Assert.notEmpty(linkageConfigRelateServiceParms, "type为1，linkageConfigRelateServiceParms 不能为空");

            checkDeviceCodesDuplicate(ruleExecution.getLinkageConfigRefDownDeviceList());
            saveLinkageServiceParams(ruleExecution.getId(), linkageConfigRelateServiceParms);

//          then  保存关联下行设备
            List<LinkageConfigRefDownDevice> linkageConfigRefDownDeviceList = ruleExecution.getLinkageConfigRefDownDeviceList();
            int sortNo = 0;
            for (LinkageConfigRefDownDevice linkageConfigRefDownDevice : linkageConfigRefDownDeviceList) {
                linkageConfigRefDownDevice.setSortNo(sortNo++);
                linkageConfigRefDownDevice.setRuleExecutionId(ruleExecution.getId());
            }
            linkageConfigRefDownDeviceService.saveOrUpdateBatch(linkageConfigRefDownDeviceList);
        }

    }

    /**
     * 检查设备编号重复
     */
    public void checkDeviceCodesDuplicate(List<LinkageConfigRefDownDevice> linkageConfigRelateServiceParms) {
        if (ObjectUtils.isEmpty(linkageConfigRelateServiceParms)) {
            return;
        }
//        List<String> deviceCodes = linkageConfigRelateServiceParms.stream().flatMap(e -> e.getLinkageConfigRefDownDeviceList().stream()).map(LinkageConfigRefDownDevice::getDeviceCode).sorted().collect(Collectors.toList());
        List<String> deviceCodes = linkageConfigRelateServiceParms.stream().map(LinkageConfigRefDownDevice::getDeviceCode).collect(Collectors.toList());
        for (String deviceCode : deviceCodes) {
            if (deviceCodes.indexOf(deviceCode) != deviceCodes.lastIndexOf(deviceCode)) {
                Assert.isTrue(false, "设备编号有重复：" + deviceCode);
            }
        }
    }

    @Override
    public List<RuleExecution> getRuleExecutions(RuleExecution ruleExecution) {
        return baseMapper.getRuleExecutions(ruleExecution);
    }

    @Override
    public IPage<RuleExecution> getRuleExecutions(Page page, RuleExecution ruleExecution) {

        return baseMapper.getRuleExecutions(page, ruleExecution);
    }

    @Override
    public List<RuleExecution> getRuleExecutionDetails(RuleExecution ruleExecution1) {

        List<RuleExecution> ruleExecutionDetails = baseMapper.getRuleExecutionDetails(ruleExecution1);
//        为下发设备排序
        for (RuleExecution ruleExecutionDetail : ruleExecutionDetails) {
            List<LinkageConfigRefDownDevice> downList = ruleExecutionDetail.getLinkageConfigRefDownDeviceList();
            downList = downList.stream().sorted((o1, o2) -> {
                if (o1.getSortNo() != null && o2.getSortNo() != null && !o1.getSortNo().equals(o2.getSortNo())) {
                    return o1.getSortNo() > o2.getSortNo() ? 1 : -1;
                }
                return 0;
            }).collect(Collectors.toList());
            ruleExecutionDetail.setLinkageConfigRefDownDeviceList(downList);
        }
        List<RuleExecution> resultNews = new ArrayList<>();

        for (RuleExecution ruleExecution : ruleExecutionDetails) {

            RuleExecution resultNew = new RuleExecution();
            BeanUtils.copyProperties(ruleExecution, resultNew);
//            resultNew.setLinkageConfigRefDownDeviceList(null);

            List<LinkageConfigRelateServiceParm> serviceParmList = ruleExecution.getLinkageConfigRelateServiceParms();
            if (ObjectUtils.isEmpty(serviceParmList)) {
                resultNews.add(resultNew);
                continue;
//                return resultNews;
            }
//        将规则联动关联服务参数 按照groupNumber 组别编号分组为map
            Map<Integer, List<LinkageConfigRelateServiceParm>> configRelateServiceParmMap = serviceParmList.stream().collect(Collectors.groupingBy(LinkageConfigRelateServiceParm::getGroupNumber));

            Set groupNumberSet = new HashSet<>();
            List<LinkageConfigRelateServiceParm> serviceParmNewList = new ArrayList<>();
            for (LinkageConfigRelateServiceParm serviceParm : serviceParmList) {
                if (!groupNumberSet.contains(serviceParm.getGroupNumber())) {
                    LinkageConfigRelateServiceParm serviceParmNew = new LinkageConfigRelateServiceParm();
                    serviceParmNew.setGroupNumber(serviceParm.getGroupNumber());
                    serviceParmNew.setDeviceUnitId(serviceParm.getDeviceUnitId());
//                serviceParmNew.setLinkageConfigRefDownDeviceList(downDevicesMap.get(serviceParm.getGroupNumber()));
                    serviceParmNew.setDeviceServiceList(getDeviceServiceListFromFlat(configRelateServiceParmMap.get(serviceParmNew.getGroupNumber())));

                    serviceParmNewList.add(serviceParmNew);
                }
                groupNumberSet.add(serviceParm.getGroupNumber());
            }

            resultNew.setLinkageConfigRelateServiceParms(serviceParmNewList);

            resultNews.add(resultNew);
        }
        return resultNews;

    }

    @Override
    public List<DeviceServices> getDeviceServiceListFromFlat(List<LinkageConfigRelateServiceParm> linkageConfigRelateServiceParmList) {
        Map<String, List<LinkageConfigRelateServiceParm>> serviceMap = linkageConfigRelateServiceParmList.stream().collect(Collectors.groupingBy(LinkageConfigRelateServiceParm::getDeviceServiceId));
        List<DeviceServices> result = new ArrayList<>();
        DeviceServices deviceServices;
        for (Entry<String, List<LinkageConfigRelateServiceParm>> entry : serviceMap.entrySet()) {
            deviceServices = new DeviceServices();
            deviceServices.setId(entry.getKey());
            List<DeviceParm> deviceParmList = getFormatDeviceParmListFromLinkageConfigRelateServiceParmList(entry.getValue());
            deviceServices.setParamList(deviceParmList);
            result.add(deviceServices);
        }

        return result;
    }


    public List<DeviceParm> getFormatDeviceParmListFromLinkageConfigRelateServiceParmList(List<LinkageConfigRelateServiceParm> linkageConfigRelateServiceParmList) {

        List<DeviceParm> result = new ArrayList();
        List<LinkageConfigRelateServiceParm> topServiceParms = linkageConfigRelateServiceParmList.stream().filter(e -> ObjectUtils.isEmpty(e.getParentId())).collect(Collectors.toList());

        for (LinkageConfigRelateServiceParm topServiceParm : topServiceParms) {
            DeviceParm deviceParm = new DeviceParm(topServiceParm.getDeviceParmId(), topServiceParm.getParmValue(), topServiceParm.getUnit(), topServiceParm.getSpecs(), topServiceParm.getArrayIndex());
            deviceParm.setName(topServiceParm.getName());
            List<DeviceParm> childParamList = new ArrayList<>();
            linkageConfigRelateServiceParmList.stream()
                .filter(e -> topServiceParm.getId() != null && topServiceParm.getId().equals(e.getParentId()))
                .sorted((a, b) -> {
                    if (a.getArrayIndex() != null && b.getArrayIndex() != null && !a.getArrayIndex().equals(b.getArrayIndex())) {
                        return a.getArrayIndex() > b.getArrayIndex() ? 1 : -1;
                    }
                    return 0;
                }).forEach(e -> {
                DeviceParm deviceParm2 = new DeviceParm(e.getDeviceParmId(), e.getParmValue(), e.getUnit(), e.getSpecs(), e.getArrayIndex());
                deviceParm2.setName(e.getName());
                childParamList.add(deviceParm2);
            });
            deviceParm.setChildParamList(childParamList);
            result.add(deviceParm);
        }
        return result;
    }


    /**
     * 保存联动配置服务参数 以及 联动配置下行设备
     */
    @Override
    public void saveLinkageServiceParams(String ruleExecutionId, List<LinkageConfigRelateServiceParm> linkageConfigRelateServiceParms) {
        //        联动配置关联下行设备
        for (LinkageConfigRelateServiceParm linkageConfigRelateServiceParm : linkageConfigRelateServiceParms) {
            Assert.notEmpty(linkageConfigRelateServiceParm.getDeviceServiceList(), "deviceServiceList 参数为空");
            for (DeviceServices deviceServices : linkageConfigRelateServiceParm.getDeviceServiceList()) {
                List<DeviceParm> deviceParmList = deviceServices.getParamList();
                Assert.notEmpty(deviceParmList, "服务参数不能为空");
                for (DeviceParm deviceParm : deviceParmList) {
                    Assert.hasLength(deviceParm.getUnit(), "参数的unit为空");
                    LinkageConfigRelateServiceParm linkageConfigRelateServiceParmNew = new LinkageConfigRelateServiceParm();
                    linkageConfigRelateServiceParmNew.setGroupNumber(linkageConfigRelateServiceParm.getGroupNumber());
                    linkageConfigRelateServiceParmNew.setDeviceUnitId(linkageConfigRelateServiceParm.getDeviceUnitId());
                    linkageConfigRelateServiceParmNew.setRuleExecutionId(ruleExecutionId);
                    linkageConfigRelateServiceParmNew.setDeviceServiceId(deviceServices.getId());
                    linkageConfigRelateServiceParmNew.setDeviceParmId(deviceParm.getId());
                    linkageConfigRelateServiceParmNew.setParmValue(deviceParm.getValue());
                    Assert.hasLength(linkageConfigRelateServiceParmNew.getDeviceParmId(), "参数 deviceParmId 为空");
                    Assert.hasLength(linkageConfigRelateServiceParmNew.getDeviceUnitId(), "参数 deviceUnitId 为空");
                    Assert.hasLength(linkageConfigRelateServiceParmNew.getDeviceServiceId(), "参数 deviceServiceId 为空");
                    Assert.notNull(linkageConfigRelateServiceParmNew.getGroupNumber(), "参数 groupNumber 为空");
                    linkageConfigRelateServiceParmService.saveOrUpdate(linkageConfigRelateServiceParmNew);
                    if (ObjectUtils.isNotEmpty(deviceParm.getChildParamList())) {
//                        添加子层级参数
                        for (DeviceParm parm : deviceParm.getChildParamList()) {
                            LinkageConfigRelateServiceParm linkageConfigRelateServiceParm2 = new LinkageConfigRelateServiceParm();
                            linkageConfigRelateServiceParm2.setParentId(linkageConfigRelateServiceParmNew.getId());

                            linkageConfigRelateServiceParm2.setGroupNumber(linkageConfigRelateServiceParm.getGroupNumber());
                            linkageConfigRelateServiceParm2.setDeviceUnitId(linkageConfigRelateServiceParm.getDeviceUnitId());
                            linkageConfigRelateServiceParm2.setRuleExecutionId(ruleExecutionId);
                            linkageConfigRelateServiceParm2.setDeviceServiceId(deviceServices.getId());
                            linkageConfigRelateServiceParm2.setDeviceParmId(parm.getId());
                            linkageConfigRelateServiceParm2.setParmValue(parm.getValue());
                            linkageConfigRelateServiceParm2.setArrayIndex(parm.getArrayIndex());

                            linkageConfigRelateServiceParmService.saveOrUpdate(linkageConfigRelateServiceParm2);
                        }
                    }
                }
            }
        }
    }

    /**
     * 根据任务产生联动
     */
    @Override
    public void generateLinkage(RuleExecution ruleExecution) {
        if (ObjectUtils.isEmpty(ruleExecution)) {
            LOGGER.info("ruleExecution 为空 in generateLinkage");
        }
        if (!RuleExecutionConstant.EXECUTION_TYPE_DOWN_SERVICE.equals(ruleExecution.getType())) {
            return;
        }
//        QueryWrapper<LinkageConfigRefDownDevice> qw = new QueryWrapper<>();
//        qw.eq("rule_execution_id", ruleExecution.getId());
//        需要按照设备设定的顺序执行
//        qw.orderByAsc("sort_no");
//        List<LinkageConfigRefDownDevice> linkageConfigRefDownDeviceList = linkageConfigRefDownDeviceService.list(qw);
        List<LinkageConfigRefDownDevice> linkageConfigRefDownDeviceList = linkageConfigRefDownDeviceService.listByRuleExecution(ruleExecution);

        for (LinkageConfigRefDownDevice linkageConfigRefDownDevice : linkageConfigRefDownDeviceList) {
            List<LinkageConfigRelateServiceParm> linkageConfigRelateServiceParms = linkageConfigRelateServiceParmMapper.selectServiceParmExtend(linkageConfigRefDownDevice);

            linkageConfigRelateServiceParms = linkageConfigRelateServiceParms.stream().filter(e -> StringUtils.isNotEmpty(e.getIdentifier())).collect(Collectors.toList());

            if (ObjectUtils.isEmpty(linkageConfigRelateServiceParms)) {
                return;
            }
            Map<String, List<LinkageConfigRelateServiceParm>> configRelateServiceParmMap = linkageConfigRelateServiceParms.stream().collect(Collectors.groupingBy(LinkageConfigRelateServiceParm::getServiceIdentifier));

            for (Entry<String, List<LinkageConfigRelateServiceParm>> entry : configRelateServiceParmMap.entrySet()) {
                Map<String, Object> downParameter = new HashMap<>();
                downParameter.put("service_id", entry.getKey());
                downParameter.put("device_id", linkageConfigRefDownDevice.getDeviceCode());
                downParameter.put("parameter", getSendDownParameter(configRelateServiceParmMap.get(entry.getKey())));
//                如果是视频类的下发服务重组下发参数，并将链接 更新到告警工单
                if (downParameter.get("service_id").equals(PLAY_BACK)) {
                    sendPlayBackServiceAndInsertLinkageLog(downParameter, ruleExecution, new Date(), linkageConfigRefDownDevice.getDeviceCode());
                } else if (downParameter.get("service_id").equals(SWITCH_CONTROL)){
                    sprayRecordsService.downSendTriggerSpray(downParameter,ruleExecution,linkageConfigRefDownDevice.getDeviceCode());
                    ApiDownResultDTO sendResult = deviceServiceService.send(downParameter);
                    insertLinkageLogAfterSendDown(ruleExecution, sendResult, linkageConfigRefDownDevice.getDeviceCode(), LinkageLogConstant.TYPE_TIME_CRON);
                } else {
                    ApiDownResultDTO sendResult = deviceServiceService.send(downParameter);
                    insertLinkageLogAfterSendDown(ruleExecution, sendResult, linkageConfigRefDownDevice.getDeviceCode(), LinkageLogConstant.TYPE_TIME_CRON);
                }
                if (configRelateServiceParmMap.entrySet().size() > 1 || linkageConfigRefDownDeviceList.size() > 1) {
                    commonService.sleep(ruleExecution.getDelayTime());
                }

            }
        }
    }


    public void insertLinkageLogAfterSendDown(RuleExecution ruleExecution, ApiDownResultDTO result, String linkageLogDeviceCode, int type) {
        //                记录联动日志
        LinkageLog linkageLog = new LinkageLog();
        linkageLog.setDeviceCode(linkageLogDeviceCode);
//                类别：1：设备报文触发，2：定时任务触发类
        linkageLog.setType(type);
        linkageLog.setSendTime(new Date());
        linkageLog.setSaveTime(new Date());

        if (ruleExecution.getRuleEngine() != null) {
            linkageLog.setTenantId(ruleExecution.getRuleEngine().getTenantId());
            linkageLog.setSpaceId(ruleExecution.getRuleEngine().getSpaceId());
            linkageLog.setRuleEngineId(ruleExecution.getRuleEngineId());
        }
        if (result != null) {
            linkageLog.setState("0".equals(result.getCode()));
            linkageLog.setResultMsg(result.getMsg());
            linkageLog.setRequestId(result.getRequestId());
            linkageLog.setResultCode(result.getCode());
        }

        linkageLogService.save(linkageLog);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteBatch(List<RuleExecution> ruleExecutions) {

        if (ObjectUtils.isEmpty(ruleExecutions)) {
            return;
        }
        List<String> ids = ruleExecutions.stream().map(RuleExecution::getId).filter(id -> StringUtils.isNotEmpty(id)).collect(Collectors.toList());
        if (ObjectUtils.isEmpty(ids)) {
            return;
        }
        removeByIds(ids);

        //  删除与之关联的
        QueryWrapper queryWrapper = new QueryWrapper();
        queryWrapper.in("rule_execution_id", ids);

        //  删除 规则关联
        linkageConfigRelateServiceParmService.remove(queryWrapper);

        //  删除 关联下行设备
        linkageConfigRefDownDeviceService.remove(queryWrapper);

        ruleExecutionRefAlarmPersonContactService.remove(queryWrapper);
    }

    @Override
    public void execute(Set<String> ruleEngineIds, Device device) {
        if (ObjectUtils.isEmpty(ruleEngineIds)) {
            LOGGER.info("规则引擎id列表为为空");
            return;
        }
//         将规则引擎以及 告警模板一起查出来
        List<RuleExecution> ruleExecutions = getRuleExecutionWithLinkageConfigRefDownDeviceList(ruleEngineIds);

        if (ObjectUtils.isEmpty(ruleExecutions)) {
            return;
        }

        ruleExecutions = alarmTypeSortFirst(ruleExecutions);

        Date date = new Date();
        for (RuleExecution ruleExecution : ruleExecutions) {
            execute(device, ruleExecution, ruleExecutions.size(), date);
        }

    }

    /**
     * 如果是告警类型的 排序到前面
     */
    private List<RuleExecution> alarmTypeSortFirst(List<RuleExecution> ruleExecutions) {
        //        如果是告警类型的 排序到前面
        List<RuleExecution> ruleExecutions2 = new ArrayList<>();
        for (int i = 0; i < ruleExecutions.size(); i++) {
            if (RuleExecutionConstant.EXECUTION_TYPE_ALARM.equals(ruleExecutions.get(i).getType())) {
                ruleExecutions2.add(ruleExecutions.get(i));
                ruleExecutions.set(i, null);
            }
        }
        for (RuleExecution ruleExecution : ruleExecutions) {
            if (ruleExecution != null) {
                ruleExecutions2.add(ruleExecution);
            }
        }
        return ruleExecutions2;
    }

    @Override
    public List<RuleExecution> getRuleExecutionWithLinkageConfigRefDownDeviceList(Set<String> ruleEngineIds) {
        if (ObjectUtils.isEmpty(ruleEngineIds)) {
            return null;
        }
        List<RuleExecution> ruleExecutionList = baseMapper.getRuleExecutionWithLinkageConfigRefDownDeviceList(ruleEngineIds);
        for (RuleExecution ruleExecution : ruleExecutionList) {
            List<LinkageConfigRefDownDevice> downList = ruleExecution.getLinkageConfigRefDownDeviceList();
            downList = downList.stream().sorted((o1, o2) -> {
                if (o1.getSortNo() != null && o2.getSortNo() != null && !o1.getSortNo().equals(o2.getSortNo())) {
                    return o1.getSortNo() > o2.getSortNo() ? 1 : -1;
                }
                return 0;
            }).collect(Collectors.toList());
            ruleExecution.setLinkageConfigRefDownDeviceList(downList);
        }
        return ruleExecutionList;
    }

    private void execute(Device device, RuleExecution ruleExecution, int ruleExecutionListSize, Date date) {

//        如果是告警工单
        if (RuleExecutionConstant.EXECUTION_TYPE_ALARM.equals(ruleExecution.getType())) {
            alarmService.generateAlarm(device, ruleExecution, date);
            return;
        }

        List<LinkageConfigRefDownDevice> linkageConfigRefDownDeviceList = ruleExecution.getLinkageConfigRefDownDeviceList();

        if (ObjectUtils.isEmpty(linkageConfigRefDownDeviceList)) {
            LOGGER.info("generateLinkage 方法检验生成联动，参数linkageConfigRefDownDeviceList 为空了！");
            return;
        }

        for (LinkageConfigRefDownDevice linkageConfigRefDownDevice : linkageConfigRefDownDeviceList) {
            if (ruleExecution.getSelfTrigger() != null && ruleExecution.getSelfTrigger() && !linkageConfigRefDownDevice.getDeviceCode().equals(device.getCode())) {
                continue;
            }

            List<LinkageConfigRelateServiceParm> linkageConfigRelateServiceParms = linkageConfigRelateServiceParmMapper.selectServiceParmExtend(linkageConfigRefDownDevice);

            linkageConfigRelateServiceParms = linkageConfigRelateServiceParms.stream().filter(e -> StringUtils.isNotEmpty(e.getIdentifier())).collect(Collectors.toList());

            if (org.springframework.util.ObjectUtils.isEmpty(linkageConfigRelateServiceParms)) {
                return;
            }

            Map<String, List<LinkageConfigRelateServiceParm>> configRelateServiceParmMap = linkageConfigRelateServiceParms.stream().collect(Collectors.groupingBy(LinkageConfigRelateServiceParm::getServiceIdentifier));

            for (Entry<String, List<LinkageConfigRelateServiceParm>> entry : configRelateServiceParmMap.entrySet()) {
                Map<String, Object> downParameter = new HashMap<>();
                downParameter.put("service_id", entry.getKey());
                downParameter.put("device_id", linkageConfigRefDownDevice.getDeviceCode());
                downParameter.put("parameter", getSendDownParameter(configRelateServiceParmMap.get(entry.getKey())));
//                如果是视频类的下发服务重组下发参数，并将链接 更新到告警工单
                if (downParameter.get("service_id").equals(PLAY_BACK)) {
                    sendPlayBackServiceAndInsertLinkageLog(downParameter, ruleExecution, date, linkageConfigRefDownDevice.getDeviceCode());
                } else if (downParameter.get("service_id").equals(SWITCH_CONTROL)){
                    sprayRecordsService.downSendTriggerSpray(downParameter,ruleExecution,linkageConfigRefDownDevice.getDeviceCode());
                    ApiDownResultDTO sendResult = deviceServiceService.send(downParameter);
                    insertLinkageLogAfterSendDown(ruleExecution, sendResult, linkageConfigRefDownDevice.getDeviceCode(), LinkageLogConstant.TYPE_DEVICE_STATUS_TRIGGER);
                } else {
                    ApiDownResultDTO sendResult = deviceServiceService.send(downParameter);
                    insertLinkageLogAfterSendDown(ruleExecution, sendResult, linkageConfigRefDownDevice.getDeviceCode(), LinkageLogConstant.TYPE_DEVICE_STATUS_TRIGGER);
                }

//                多个执行器间、一个执行器多个服务之间、一个执行器一个服务的多个设备之间 均进行延时
                if (configRelateServiceParmMap.entrySet().size() > 1 || linkageConfigRefDownDeviceList.size() > 1 || ruleExecutionListSize > 1) {
                    commonService.sleep(ruleExecution.getDelayTime());
                }
            }
        }
    }

    /**
     * 多线程执行-重构参数，下发视频保存，插入下发日志
     * @param downParameter
     * @param ruleExecution
     * @param date
     * @param deviceCode
     */
    private void sendPlayBackServiceAndInsertLinkageLog(Map<String, Object> downParameter, RuleExecution ruleExecution, Date date, String deviceCode) {
        pool.execute(() -> {
            boolean updateAlarm = rebuildVideoDownParamAndUpdateAlarm(downParameter, ruleExecution, date);
            if (updateAlarm) {
                ApiDownResultDTO sendResult = deviceServiceService.send(downParameter);
                insertLinkageLogAfterSendDown(ruleExecution, sendResult, deviceCode, LinkageLogConstant.TYPE_DEVICE_STATUS_TRIGGER);
            }
        });
    }

    @Test
    public void test() {
        String s = "https://nb-video-test.oss-cn-hangzhou.aliyuncs.com/34020000001323000122@34020000001323000122/2021-03-18 10:25:00-2021-03-18 11:10:00.m3u8";
        String deviceCode = "34020000001323000122";
        String startStr = "2021-03-18 10:25:00";
        String endStr = "2021-03-18 11:10:00";
        String url = null;
        try {
            url = "https://nb-video-test.oss-cn-hangzhou.aliyuncs.com" + "/" + URLEncoder.encode(deviceCode + "@" + deviceCode, "UTF-8") + "/" + URLEncoder.encode(startStr + "-" + endStr + ".m3u8", "UTF-8");
            url = url.replaceAll("\\+", "%20");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        System.out.println(url);

    }

    /**
     * 重组下发参数，并将链接 更新到告警工单
     *
     * @param date 告警时候的时间
     */
    private boolean rebuildVideoDownParamAndUpdateAlarm(Map<String, Object> downParameter, RuleExecution ruleExecution, Date date) {
//        是否更新告警
        boolean updateAlarm = false;
        HashMap<String, Object> param = (HashMap<String, Object>) downParameter.get("parameter");
//         处理 时间 end start 为 yyyy-MM xx的格式
        param.put("start", DateUtil.getYYYYMMDDHHMMSSDate(new Date(date.getTime() + 1000L * Long.parseLong(param.get("start").toString()))));
        Date endTime = new Date(date.getTime() + 1000L * Long.parseLong(param.get("end").toString()));
        long currentTimeMillis = System.currentTimeMillis();
        if (endTime.getTime() > currentTimeMillis) {
            commonService.sleep((endTime.getTime() - currentTimeMillis) / 1000.0);
        }

        param.put("end", DateUtil.getYYYYMMDDHHMMSSDate(endTime));
        param.put("isAlarm", true);
        if (ObjectUtils.isEmpty(param.get("action"))) {
            param.put("action", PLAY_BACK);
        }
        QueryWrapper<Alarm> qw = new QueryWrapper();
        qw.select("id");
        qw.eq("rule_engine_id", ruleExecution.getRuleEngineId());
        qw.eq("alarm_time", DateUtil.getYYYYMMDDHHMMSSDate(date));
        List<Alarm> list = alarmService.list(qw);
        if (list.isEmpty()) {
            return updateAlarm;
        }
//        通过约定好的规则生成 视频链接url
        String url = getVideoUrl(downParameter);
        for (Alarm alarm : list) {
            alarm.setVideoUrl(url);
            alarmService.updateById(alarm);
            if (updateAlarm == false) {
                updateAlarm = true;
            }
        }
        return updateAlarm;

    }

    private String getVideoUrl(Map<String, Object> downParameter) {
        String deviceCode = (String) downParameter.get("device_id");
        HashMap<String, Object> param = (HashMap<String, Object>) downParameter.get("parameter");
        String startStr = (String) param.get("start");
        String endStr = (String) param.get("end");
        String url = null;
        try {
//            url = nbVideoUrl + "/" + URLEncoder.encode(deviceCode + "@" + deviceCode, "UTF-8") + "/" + URLEncoder.encode(startStr + "-" + endStr + ".m3u8", "UTF-8");
            url = nbVideoUrl + "/" + URLEncoder.encode(deviceCode, "UTF-8") + "/" + URLEncoder.encode(startStr + "-" + endStr + ".m3u8", "UTF-8");
            url = url.replaceAll("\\+", "%20");

        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }

        return url;
    }


    @Override
    public Map<String, Object> getSendDownParameter(List<LinkageConfigRelateServiceParm> linkageConfigRelateServiceParmList) {

        Map<String, Object> result = new HashMap<>();

        List<LinkageConfigRelateServiceParm> topServiceParms = linkageConfigRelateServiceParmList.stream().filter(e -> ObjectUtils.isEmpty(e.getParentId())).collect(Collectors.toList());

        for (LinkageConfigRelateServiceParm topServiceParm : topServiceParms) {
            List<DeviceParm> childParamList = new ArrayList<>();
            linkageConfigRelateServiceParmList.stream()
                .filter(e -> topServiceParm.getId() != null && topServiceParm.getId().equals(e.getParentId()))
                .sorted((a, b) -> {
                    if (a.getArrayIndex() != null && b.getArrayIndex() != null && !a.getArrayIndex().equals(b.getArrayIndex())) {
                        return a.getArrayIndex() > b.getArrayIndex() ? 1 : -1;
                    }
                    return 0;
                }).forEach(e -> childParamList.add(new DeviceParm(e.getIdentifier(), e.getParmValue(), e.getUnit(), e.getArrayIndex())));

            Object childParam = getChildParam(topServiceParm, childParamList);

            switch (topServiceParm.getUnit()) {
                case PhysicalModelTypeConstant.ARRAY_SIMPLE:
                    result.put(topServiceParm.getIdentifier(), JSONObject.parseArray(topServiceParm.getParmValue()));
                    break;
                case PhysicalModelTypeConstant.OBJECT:
                    result.put(topServiceParm.getIdentifier(), childParam);
                    break;
                case PhysicalModelTypeConstant.ARRAY_OBJECT:
                    result.put(topServiceParm.getIdentifier(), childParam);
                    break;
                default:
                    result.put(topServiceParm.getIdentifier(), topServiceParm.getParmValue());
                    break;
            }
        }

        return result;
    }


    public Object getChildParam(LinkageConfigRelateServiceParm topServiceParm, List<DeviceParm> childParamList) {

        if (PhysicalModelTypeConstant.OBJECT.equals(topServiceParm.getUnit())) {
            Map<String, Object> childParamMap = new HashMap<>();
            for (DeviceParm deviceParm : childParamList) {
                childParamMap.put(deviceParm.getIdentifier(), deviceParm.getValue());
            }
            return childParamMap;
        } else if (PhysicalModelTypeConstant.ARRAY_OBJECT.equals(topServiceParm.getUnit())) {
            List<Map<String, Object>> childParamMapList = new ArrayList<>();
            List<List<DeviceParm>> listList = new ArrayList();
            Map<Integer, List<DeviceParm>> map = childParamList.stream().collect(Collectors.groupingBy(e -> e.getArrayIndex()));
            map.entrySet().stream().sorted(Entry.comparingByKey()).forEachOrdered(a -> listList.add(a.getValue()));
            for (List<DeviceParm> deviceParmList : listList) {
                Map map2 = new HashMap();
                deviceParmList.forEach(deviceParm -> map2.put(deviceParm.getIdentifier(), deviceParm.getValue()));
                childParamMapList.add(map2);
            }
            return childParamMapList;

        } else {
            return null;
        }
    }


}
